﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Net;
using System.Threading;

namespace IcqSharp
{
	public class ServiceFamilyHandler //: FamilyHandler
	{
		public static void Handle(MemoryStream stream, SNACHeader header)
		{
			switch (header.Subtype)
			{
				case (UInt16)ServiceFamilySubtype.ServerReady:
					HandleServerReady(stream, header); break;

				case (UInt16)ServiceFamilySubtype.ServerFamilies2:
					HandleServerFamilies2(stream, header); break;

				case (UInt16)ServiceFamilySubtype.ServerRateInfo:
					HandleServerRateInfo(stream, header); break;

				case (UInt16)ServiceFamilySubtype.ServerPause:
					HandleServerPause(stream, header); break;

				case (UInt16)ServiceFamilySubtype.ServerMigrationReq:
					HandleServerMigrationReq(stream, header); break;

				case (UInt16)ServiceFamilySubtype.ServerNameInfo:
					HandleServerNameInfo(stream, header); break;

				case (UInt16)ServiceFamilySubtype.ServerRateChange:
					HandleServerRateChange(stream, header); break;

				case (UInt16)ServiceFamilySubtype.ServerRedirectService:
					HandleServerRedirectService(stream, header); break;

				case (UInt16)ServiceFamilySubtype.ServerExtStatus:
					HandleServerExtStatus(stream, header); break;

				case (UInt16)ServiceFamilySubtype.Error:
					HandleError(stream, header); break;

				case (UInt16)ServiceFamilySubtype.ServerMotd:
					break;

				default:
					HandleUnknownSNAC(stream, header); break;
			}
		}

		private static void HandleServerReady(MemoryStream stream, SNACHeader header)
		{
            Log.Write("Got  Data.Service.ServerReady");

			// Server is ready and is requesting my Family versions
			// Sending my Families

			// This packet is a response to SRV_FAMILIES SNAC(1,3).
			// This tells the server which SNAC families and their corresponding
			// versions which the client understands. This also seems to identify
			// the client as an ICQ vice AIM client to the server.
			// Miranda mimics the behaviour of ICQ 6

			var p = new Packet();

			p.WriteFNACHeader((UInt16)Family.Service,
				(UInt16)ServiceFamilySubtype.ClientFamilies);

			p.Data.Write((UInt32)0x00220001);
			p.Data.Write((UInt32)0x00010004);
			p.Data.Write((UInt32)0x00130004);
			p.Data.Write((UInt32)0x00020001);
			p.Data.Write((UInt32)0x00030001);
			p.Data.Write((UInt32)0x00150001);
			p.Data.Write((UInt32)0x00040001);
			p.Data.Write((UInt32)0x00060001);
			p.Data.Write((UInt32)0x00090001);
			p.Data.Write((UInt32)0x000A0001);
			p.Data.Write((UInt32)0x000B0001);

			p.Send();

            Log.Write("Sent Data.Service.ClientFamilies");
		}
		private static void HandleServerFamilies2(MemoryStream stream, SNACHeader header)
		{
            Log.Write("Got  Data.Service.ServerFamilies");

			// This is a reply to CLI_FAMILIES and it tells the client which families
			// and their versions that this server understands.
			// We send a rate request packet

			// Server told me his Family versions
			// Requesting Rate Information

			var p = new Packet();
			p.WriteFNACHeader((UInt16)Family.Service,
				(UInt16)ServiceFamilySubtype.ClientReqRateInfo);
			p.Send();

            Log.Write("Sent Data.Service.ClientRequestRateInfo");
		}
		private static void HandleServerRateInfo(MemoryStream stream, SNACHeader header)
		{
            Log.Write("Got  Data.Service.ServerRateInfo");

			// Server sent Rate Info

			// init rates management
			Rates.Initialize(stream);

			// ack rate levels
			// Sending Rate Info Ack
			var p = Rates.InitAckPacket();
			p.Send();

			// CLI_REQINFO - This command requests from the server certain information
			// about the client that is stored on the server.
			// Sending CLI_REQINFO
			p = new Packet();
			p.WriteFNACHeader((UInt16)Family.Service,
				(UInt16)ServiceFamilySubtype.ClientReqInfo);
			p.Send();

            Log.Write("Sent Data.Service.ClientRequestInfo");

			return;


			if (Session.SsiEnabled)
			{
				CookieServlistAction ack;
				UInt32 Cookie = 0;

				UInt32 LastUpdate = Session.SrvLastUpdate;
				UInt16 RecordCount = Session.SrvRecordCount;

				// CLI_REQLISTS - we want to use SSI
				// Requesting roster rights
				p = new Packet();
				p.WriteFNACHeader((UInt16)Family.Lists, (UInt16)ListsFamilySubtype.CliReqLists);
				p.Data.WriteTLV(0x0B, (UInt16)0x000F); // mimic ICQ 6
				p.Send();

				if (RecordCount == 0) // CLI_REQROSTER
				{
					// we do not have any data - request full list
					// Requesting full roster
					p = new Packet();
					ack = new CookieServlistAction();
					if (ack != null)
					{
						// we try to use standalone cookie if available
						ack.Action = (UInt16)Actions.CheckRoster; // loading list
						//Cookie = AllocateCookie((Byte)CookieType.ServerList, (UInt16)ListsFamilySubtype.CliRequest, 0, ack);
					}
					else // if not use that old fake
						Cookie = (UInt32)ListsFamilySubtype.CliRequest << 0x10;

					p.WriteFNACHeader((UInt16)Family.Lists, (UInt16)ListsFamilySubtype.CliRequest, 0, Cookie);
					p.Send();
				}
				else // CLI_CHECKROSTER
				{
					// Requesting roster check

					p = new Packet();

					ack = new CookieServlistAction();

					// TODO: rewrite - use get list service for empty list
					if (ack != null)
					{
						// we try to use standalone cookie if available
						ack.Action = (UInt32)Actions.CheckRoster; // loading list
						//Cookie = AllocateCookie(CookieType.ServerList, ListsFamilySubtype.CliCheck, 0, ack);
					}
					else
						// if not use that old fake
						Cookie = (UInt32)ListsFamilySubtype.CliCheck << 0x10;

					p.WriteFNACHeader((UInt16)Family.Lists, (UInt16)ListsFamilySubtype.CliCheck, 0, Cookie);

					// check if it was not changed elsewhere (force reload, set that setting to zero)
					//if (IsServerGroupsDefined())
					{
						p.Data.Write((UInt32)LastUpdate);  // last saved time
						p.Data.Write((UInt16)RecordCount); // number of records saved
					}
					//else
					{
						// we need to get groups info into DB, force receive list
						//p.Data.Write((UInt32)0); // last saved time
						//p.Data.Write((UInt16)0); // number of records saved
					}
					p.Send();
				}
			}

			// CLI_REQLOCATION
			// Requesting Location rights
			p = new Packet();
			p.WriteFNACHeader((UInt16)Family.Location,
				(UInt16)LocationFamilySubtype.CliReqRights);
			p.Send();

			// CLI_REQBUDDY
			// Requesting Client-side contactlist rights
			p = new Packet();
			p.WriteFNACHeader((UInt16)Family.Buddy,
				(UInt16)BuddyFamilySubtype.CliReqBuddy);
			// Query flags: 1 = Enable Avatars
			//              2 = Enable offline status message notification
			//              4 = Enable Avatars for offline contacts
			//              8 = Use reject for not authorized contacts
			p.Data.WriteTLV(0x05, (UInt16)0x0007);
			p.Send();

			// CLI_REQICBM
			p = new Packet();
			p.WriteFNACHeader((UInt16)Family.Message,
				(UInt16)MessageFamilySubtype.CliReqICBM);
			p.Send();

			// CLI_REQBOS
			p = new Packet();
			p.WriteFNACHeader((UInt16)Family.BOS,
				(UInt16)PrivacyFamilySubtype.ReqRights);
			p.Send();
		}
		private static void HandleServerPause(MemoryStream stream, SNACHeader header)
		{
			// Server is going down in a few seconds... (Flags: %u), SnacHeader.wFlags
			// This is the list of groups that we want to have on the next server

			var p = new Packet();
			p.WriteFNACHeader((UInt16)Family.Service,
				(UInt16)ServiceFamilySubtype.ClientPauseAck);
			p.Data.Write((UInt16)Family.Service);
			p.Data.Write((UInt16)Family.Service);
			p.Data.Write((UInt16)Family.Lists);
			p.Data.Write((UInt16)Family.Location);
			p.Data.Write((UInt16)Family.Buddy);
			p.Data.Write((UInt16)Family.Extensions);
			p.Data.Write((UInt16)Family.Message);
			p.Data.Write((UInt16)0x06);
			p.Data.Write((UInt16)Family.BOS);
			p.Data.Write((UInt16)Family.Lookup);
			p.Data.Write((UInt16)Family.Stats);
			p.Send();
		}
		private static void HandleServerMigrationReq(MemoryStream stream, SNACHeader header)
		{
			//    // Server migration requested (Flags: %u), SnacHeader.wFlags

			//    // Unknown, seen: 0
			//    stream.Seek(+2, SeekOrigin.Current);

			//    var chain = ReadIntoTLVChain(stream, client.Available, 0);

			//        if (info->cookieDataLen > 0)
			//            SAFE_FREE((void**)&info->cookieData);

			//        info->newServer = chain->getString(0x05, 1);
			//        info->newServerSSL = chain->getNumber(0x8E, 1);
			//        info->cookieData = (BYTE*)chain->getString(0x06, 1);
			//        info->cookieDataLen = chain->getLength(0x06, 1);

			//        disposeChain(&chain);

			//        if (!info->newServer || !info->cookieData)
			//        {
			//            icq_LogMessage(LOG_FATAL, LPGEN("A server migration has failed because the server returned invalid data. You must reconnect manually."));
			//            SAFE_FREE(&info->newServer);
			//            SAFE_FREE((void**)&info->cookieData);
			//            info->cookieDataLen = 0;
			//            info->newServerReady = 0;
			//            return;
			//        }

			//        NetLog_Server("Migration has started. New server will be %s", info->newServer);

			//        m_iDesiredStatus = m_iStatus;
			//        SetCurrentStatus(ID_STATUS_CONNECTING); // revert to connecting state

			//        info->newServerReady = 1;
			//        info->isMigrating = 1;
			//    }
		}
		private static void HandleServerNameInfo(MemoryStream stream, SNACHeader header)
		{
            Log.Write("Got  Data.Service.ServerNameInfo");

			// Received self info

			// skip UIN
			var uin_len = (Byte)stream.ReadByte();
			stream.Skip(uin_len);

			// skip warning level and user class
			stream.Skip(4);

			// during the login sequence?
			//if (header.Ref == (UInt32)ServiceFamilySubtype.ClientReqInfo << 0x10)
			{ 
				// TLV(x01) User type?
				// TLV(x0C) Empty CLI2CLI Direct connection info
				// TLV(x0A) External IP
				// TLV(x0F) Number of seconds that user has been online
				// TLV(x03) The online since time.
				// TLV(x0A) External IP again
				// TLV(x22) Unknown
				// TLV(x1E) Unknown: empty.
				// TLV(x05) Member of ICQ since.
				// TLV(x14) Unknown
				var chain = new TLVChain(stream);

				// external IP
				Session.ExternalIP = chain.GetDWord(0x0A, 1);
                var ip = new IPAddress(Session.ExternalIP);

				// member since
				var _member_since = chain.GetDWord(0x05, 1);
				Session.MemberSince = new DateTime(_member_since);

				// logon
				var _logon_since = chain.GetDWord(0x03, 1);
				Session.LogonSince = new DateTime(_logon_since);

				// If we are in SSI mode, this is sent after the list is acked instead
				// to make sure that we don't set status before seing the visibility code
				if (Session.SsiEnabled || Session.IsMigrating)
					HandleServUINSettings((int)Session.Port);
			}

            ; //else if (m_hNotifyNameInfoEvent)
				// Just notify that the set status note & mood process is finished
				//SetEvent(m_hNotifyNameInfoEvent);
		}

        

		private static void SendUserInfo()
		{ 
			UInt16 add_len = 0;
		
            if (Session.AimEnabled) add_len += 16;
            if (Session.AvatarsEnabled) add_len += 16;
            if (Session.xStatusEnabled && Session.xStatus != 0) add_len += 16;
		
            ////MIM/PackName
            //bool bHasPackName = false;
            //DBVARIANT dbv;
            //if ( !DBGetContactSettingString(NULL, "ICQCaps", "PackName", &dbv )) {
            //    //MIM/PackName
            //    bHasPackName = true;
            //    wAdditionalData += 16;
            //}

            var p = new Packet();
            p.WriteFNACHeader((UInt16)Family.Location,
                (UInt16)LocationFamilySubtype.SetUserInfo);

			// TLV(5): capability data
            p.Data.Write((UInt16)0x0005);
			p.Data.Write((UInt16)(48 + add_len));

            p.WriteShortCapability(0x1349); // AIM_CAPS_ICQSERVERRELAY
            p.WriteShortCapability(0x134E); // CAP_UTF8MSGS
            if (Session.AvatarsEnabled)
                p.WriteShortCapability(0x134C); // CAP_DEVILS
            if (Session.AimEnabled)
                p.WriteShortCapability(0x134D); // CAP_AIM_COMPATIBLE
            if (Session.xStatusEnabled && Session.xStatus != 0)
                ; // p.Write(xStatusCaps[Session.xStatus - 1]);
            p.WriteShortCapability(0x1344);      // CAP_ICQDIRECT


			p.Data.Write((UInt32)0x4D697261);       // Miranda Signature
            p.Data.Write((UInt32)0x6E64614D);
            p.Data.Write((UInt32)0x11111111);       // Miranda version
            p.Data.Write((UInt32)0x11111111);       // ICQ plugin version

            ////MIM/PackName
            //if ( bHasPackName ) {
            //    packBuffer(&packet, (BYTE*)dbv.pszVal, 0x10);
            //    ICQFreeVariant(&dbv);
            //}

			p.Send();

            Log.Write("Sent Data.Service.ClientUserInfo");
		}
        private static void SendEntireListServ(UInt16 Family, UInt16 Subtype, Int32 listType)
        {
            //HANDLE hResumeContact = NULL;

            //do
            //{ // server doesn't seem to be able to cope with packets larger than 8k
            //    // send only about 100contacts per packet
            //    char *szList = buildUinList(listType, 0x3E8, &hResumeContact);
            //    int nListLen = strlennull(szList);

            //    if (nListLen)
            //    {
            //        icq_packet packet;

            //        serverPacketInit(&packet, (WORD)(nListLen + 10));
            //        packFNACHeader(&packet, wFamily, wSubtype);
            //        packBuffer(&packet, (LPBYTE)szList, (WORD)nListLen);
            //        sendServPacket(&packet);
            //    }

            //    SAFE_FREE((void**)&szList);
            //}
            //while (hResumeContact);

            Log.Write("SKIP Data.Service.EntireListServ");
        }

        private static void SendOwnerInfoRequest()
        {
            var CookieData = new CookieDirectoryData();
	        CookieData.RequestType = (Byte)CookieDirectoryRequest.InfoOwner;

            var cookie = Cookies.AllocateCookie(CookieType.DirectoryQuery, 0, 0, CookieData);
            var data_len = Session.LocalUIN.ToString().Length + 4;

            var p = new Packet();
            p.packServIcqDirectoryHeader((UInt16)(data_len + 8), 0x0FA0, 0x0002, (UInt16)cookie);
	        p.Data.Write((UInt16)0x03);   // with interests (ICQ6 uses 2 at login)
	        p.Data.Write((UInt32)0x01);
	        p.Data.Write((UInt16)data_len);
            var uid = Session.LocalUIN.ToString();
            p.Data.WriteTLV(0x32, uid.GetAsciiBytes());
            p.Send();
        }

        private static UInt16 GetMyStatusFlags()
        {
            var flags = (UInt16)0;

	        if (Session.WebAwareEnabled) 
                flags |= (UInt16)StatusFlags.WebAware;

            flags |= (UInt16)StatusFlags.ShowIP;
            flags |= (UInt16)StatusFlags.Birthday;

	        switch (Session.DcType) {
                case 0: break;
                case 1: flags |= (UInt16)StatusFlags.DcContacts; break;
                case 2: flags |= (UInt16)StatusFlags.DcAuthorized; break;
                default: flags |= (UInt16)StatusFlags.DcDisabled; break; }

	        return flags;
        }

        private static void SetCurrentStatus(IcqStatus new_status)
        {
	        var old_status = Session.Status;
	        Session.Status = new_status;

            ; // BroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, old_status, new_status);
        }


		private static void HandleServUINSettings(Int32 nPort)
		{
			Packet p;

			SendUserInfo();
			
			// SNAC 3,4: Tell server who's on our list (deprecated)
			// SNAC 3,15: Try to add unauthorised contacts to temporary list
			; //sendEntireListServ(ICQ_BUDDY_FAMILY, ICQ_USER_ADDTOTEMPLIST, BUL_ALLCONTACTS);

			if (Session.DesiredStatus == IcqStatus.Invisible)
			{
				/* Tell server who's on our visible list (deprecated) */
				//if (!m_bSsiEnabled)
				//	sendEntireListServ(ICQ_BOS_FAMILY, ICQ_CLI_ADDVISIBLE, BUL_VISIBLE);
				//else
				//	updateServVisibilityCode(3);
			}
            else
			{
				///* Tell server who's on our invisible list (deprecated) */
				//if (!m_bSsiEnabled)
				//	sendEntireListServ(ICQ_BOS_FAMILY, ICQ_CLI_ADDINVISIBLE, BUL_INVISIBLE);
				//else
				//	updateServVisibilityCode(4);
			}

			// SNAC 1,1E: Set status
			{
                var random = new Random((Int32)DateTime.Now.Ticks);

                Thread.Sleep(10); var rnd1 = (UInt32)random.Next();
                Thread.Sleep(10); var rnd2 = (UInt32)random.Next();

				UInt32 DirectCookie = rnd1 ^ (rnd2 << 16);


				// Get status
                var status = (UInt16)Session.DesiredStatus;

				// Get status note & mood
                String StatusNote = "STATUS NOTE";
                ; // = PrepareStatusNote(m_iDesiredStatus);
				Byte xStatus = 0; 
                ; // = getContactXStatus(NULL);
				//char szMoodData[32];

				// prepare mood id
				//if (m_bMoodsEnabled && bXStatus && moodXStatus[bXStatus-1] != -1)
				//	null_snprintf(szMoodData, SIZEOF(szMoodData), "icqmood%d", moodXStatus[bXStatus-1]);
				//else
				//	szMoodData[0] = '\0';

                var MoodData = "";

				//! Tricky code, this ensures that the status note will be saved to the directory
				//SetStatusNote(szStatusNote, m_bGatewayMode ? 5000 : 2500, TRUE);


				var StatusNoteLen = (UInt16)StatusNote.Length;
				var StatusMoodLen = (UInt16)MoodData.Length;
				var SessionDataLen = (UInt16)(StatusNoteLen > 0 ? StatusNoteLen + 4 : 0) + 4 + StatusMoodLen + 4;

                p = new Packet();
                p.WriteFNACHeader((UInt16)Family.Service, 
                    (UInt16)ServiceFamilySubtype.ClientSetStatus);

                p.Data.Write((UInt32)0x00060004);       // TLV 6: Status mode and security flags
                p.Data.Write(GetMyStatusFlags());       // Status flags
				p.Data.Write(status);                   // Status
				p.Data.WriteTLV(0x8, (UInt16)0xA06);    // TLV 8: Independent Status Messages
				p.Data.Write((UInt32)0x000c0025);       // TLV C: Direct connection info
				p.Data.Write(Session.ExternalIP);
				p.Data.Write(Session.Port);
				p.Data.Write((Byte)0x04);               // TCP/FLAG firewall settings
				p.Data.Write((UInt16)8);
				p.Data.Write(DirectCookie);             // DC Cookie
				p.Data.Write((UInt32)0x50);             // Web front port
				p.Data.Write((UInt32)0x03);             // Client features
				p.Data.Write((UInt32)0x7fffffff);       // Abused timestamp - Unicode variant
				p.Data.Write((UInt32)0x11111111);       // Abused timestamp - ICQ_PLUG_VERSION
				p.Data.Write((UInt32)0x00000000);       // Timestamp
				p.Data.Write((UInt16)0x0000);           // Unknown
				p.Data.WriteTLV(0x1F, (UInt16)0);

                // Pack session data
				if (SessionDataLen > 0)
				{ 
                    
					p.Data.Write((UInt16)0x1D);                 // TLV 1D
					p.Data.Write((UInt16)SessionDataLen);       // TLV length
					p.Data.Write((UInt16)0x02);                 // Item Type
					
                    if (StatusNoteLen > 0)
					{
                        // Flags + Item Length
						p.Data.Write((UInt16)(0x400|(UInt16)(StatusNoteLen + 4)));

                        // Text Length
						p.Data.Write((UInt16)StatusNoteLen);

                        // Text
						p.Data.Write(StatusNote.GetUnicodeBytes());

                        // Encoding not specified (utf-8 is default)
						p.Data.Write((UInt16)0);                    
					}
					else
                    {
                        // Flags + Item Length
						p.Data.Write((UInt16)0);                  
                    }

                    // Item Type
					p.Data.Write((UInt16)0x0E);      

                    // Flags + Item Length
					p.Data.Write((UInt16)StatusMoodLen);
     
                    // Text
					if (StatusMoodLen > 0)
                        p.Data.Write(MoodData.GetAsciiBytes());

					// Save current status note & mood
					//setSettingStringUtf(NULL, DBSETTING_STATUS_NOTE, szStatusNote);
					//setSettingString(NULL, DBSETTING_STATUS_MOOD, szMoodData);
				}
				
				p.Send();
                Log.Write("Sent Data.Service.ClientSetStatus");
			}

			// SNAC 1,11
            p = new Packet();
            p.WriteFNACHeader((UInt16)Family.Service,
                (UInt16)ServiceFamilySubtype.ClientSetIdle);
			p.Data.Write((UInt32)0x00000000);
			p.Send();
            Log.Write("Sent Data.Service.ClientSetIdle");
            Session.IdleAllow = false;

			// Change status
			SetCurrentStatus(Session.DesiredStatus);

			// Finish Login sequence (imitate ICQ 6 behaviour)
			p = new Packet();
            p.WriteFNACHeader((UInt16)Family.Service,
                (UInt16)ServiceFamilySubtype.ClientReady);
			p.Data.Write((UInt32)0x00220001); 
			p.Data.Write((UInt32)0x0110164f);
			p.Data.Write((UInt32)0x00010004);
			p.Data.Write((UInt32)0x0110164f);
			p.Data.Write((UInt32)0x00130004);
			p.Data.Write((UInt32)0x0110164f);
			p.Data.Write((UInt32)0x00020001);
			p.Data.Write((UInt32)0x0110164f);
			p.Data.Write((UInt32)0x00030001);
			p.Data.Write((UInt32)0x0110164f);
			p.Data.Write((UInt32)0x00150001);
			p.Data.Write((UInt32)0x0110164f);
			p.Data.Write((UInt32)0x00040001);
			p.Data.Write((UInt32)0x0110164f);
			p.Data.Write((UInt32)0x00060001);
			p.Data.Write((UInt32)0x0110164f);
			p.Data.Write((UInt32)0x00090001);
			p.Data.Write((UInt32)0x0110164f);
			p.Data.Write((UInt32)0x000A0001);
			p.Data.Write((UInt32)0x0110164f);
			p.Data.Write((UInt32)0x000B0001);
			p.Data.Write((UInt32)0x0110164f);
			p.Send();
            Log.Write("Sent Data.Service.ClientReady");

			// Yeehah, login sequence complete

			// login sequence is complete enter logged-in mode
			Session.LoggedIn = true;
			Session.ConnectionLost = false;

			// enable auto info-update routine
            ; // icq_EnableUserLookup(TRUE);

			if (!Session.IsMigrating)
			{ 
                // Get Offline Messages Reqeust
				var ack = new CookieOfflineMessages();
				var cookie = Cookies.AllocateCookie(CookieType.OfflineMessage, 
                    (UInt16)MessageFamilySubtype.CliReqOffline, 0, ack);

                p = new Packet();
                p.WriteFNACHeader((UInt16)Family.Message,
                    (UInt16)MessageFamilySubtype.CliReqOffline, 0, cookie);
                p.Send();
                Log.Write("Sent Data.Message.ClientRequireOffline");


				// Update our information from the server
				; //sendOwnerInfoRequest();

				// Request info updates on all contacts
				; //icq_RescanInfoUpdate();

				// Start sending Keep-Alive packets
				; //StartKeepAlive(info);

				; // if (m_bAvatarsEnabled)
				{ // Send SNAC 1,4 - request avatar family 0x10 connection
					; // icq_requestnewfamily(ICQ_AVATAR_FAMILY, &CIcqProto::StartAvatarThread);

					; //m_avatarsConnectionPending = TRUE;
					// Requesting Avatar family entry point
				}
			}
			Session.IsMigrating = false;

            ; // if (Session.AimEnabled)
			{
                //char **szAwayMsg = NULL;
				//icq_lock l(m_modeMsgsMutex);

				//szAwayMsg = MirandaStatusToAwayMsg(m_iStatus);
				//if (szAwayMsg)
				//	icq_sendSetAimAwayMsgServ(*szAwayMsg);
			}
		}


		private static void HandleServerRateChange(MemoryStream stream, SNACHeader header)
		{
			//    if (wBufferLength >= 2)
			//    {
			//        WORD wStatus;
			//        WORD wClass;
			//        DWORD dwLevel;
			//        // We now have global rate management, although controlled are only some
			//        // areas. This should not arrive in most cases. If it does, update our
			//        // local rate levels & issue broadcast.
			//        unpackWord(&pBuffer, &wStatus);
			//        unpackWord(&pBuffer, &wClass);
			//        pBuffer += 20;
			//        unpackDWord(&pBuffer, &dwLevel);

			//        m_ratesMutex->Enter();
			//        m_rates->updateLevel(wClass, dwLevel);
			//        m_ratesMutex->Leave();

			//        if (wStatus == 2 || wStatus == 3)
			//        { // this is only the simplest solution, needs rate management to every section
			//            BroadcastAck(NULL, ICQACKTYPE_RATEWARNING, ACKRESULT_STATUS, (HANDLE)wClass, wStatus);
			//            if (wStatus == 2)
			//                NetLog_Server("Rates #%u: Alert", wClass);
			//            else
			//                NetLog_Server("Rates #%u: Limit", wClass);
			//        }
			//        else if (wStatus == 4)
			//        {
			//            BroadcastAck(NULL, ICQACKTYPE_RATEWARNING, ACKRESULT_STATUS, (HANDLE)wClass, wStatus);
			//            NetLog_Server("Rates #%u: Clear", wClass);
			//        }
			//    }

		}
		private static void HandleServerRedirectService(MemoryStream stream, SNACHeader header)
		{
			// reply to family request, got new connection point
			//{
			//    TLVChain Chain = null;
			//    CookieFamilyRequest CookieData;

			//    if (!(Chain = ReadIntoTLVChain(stream, client.Available, 0)))
			//    {
			//        // Received Broken Redirect Service SNAC(1,5)
			//        Debugger.Break();
			//        break;
			//    }
			//    var Family = Chain.GetWord(0x0D, 1);

			//    // pick request data
			//    if ((!FindCookie(pSnacHeader->dwRef, NULL, (void**)&pCookieData)) || (pCookieData->wFamily != wFamily))
			//    {
			//        disposeChain(&pChain);
			//        NetLog_Server("Received unexpected SNAC(1,5), skipping.");
			//        break;
			//    }

			//    FreeCookie(pSnacHeader->dwRef);

			//    { // new family entry point received
			//        char *pServer = pChain->GetString(0x05, 1);
			//        BYTE bServerSSL = pChain->GetNumber(0x8E, 1);
			//        char *pCookie = pChain->GetString(0x06, 1);
			//        WORD wCookieLen = pChain->GetLength(0x06, 1);

			//        if (!pServer || !pCookie)
			//        {
			//            NetLog_Server("Server returned invalid data, family unavailable.");

			//            SAFE_FREE(&pServer);
			//            SAFE_FREE(&pCookie);
			//            SAFE_FREE((void**)&pCookieData);
			//            disposeChain(&pChain);
			//            break;
			//        }

			//        // Get new family server ip and port
			//        WORD wPort = info->wServerPort; // get default port
			//        parseServerAddress(pServer, &wPort);

			//        // establish connection
			//        NETLIBOPENCONNECTION nloc = {0};
			//        if (m_bGatewayMode)
			//            nloc.flags |= NLOCF_HTTPGATEWAY;
			//        nloc.szHost = pServer; 
			//        nloc.wPort = wPort;

			//        HANDLE hConnection = NetLib_OpenConnection(m_hServerNetlibUser, wFamily == ICQ_AVATAR_FAMILY ? "Avatar " : NULL, &nloc);

			//        if (hConnection == NULL)
			//        {
			//            NetLog_Server("Unable to connect to ICQ new family server.");
			//        } // we want the handler to be called even if the connecting failed
			//        else if (bServerSSL)
			//        { /* Start SSL session if requested */

			//            // (%d) Starting SSL negotiation", CallService(MS_NETLIB_GETSOCKET, (WPARAM)hConnection, 0))

			//            if (!CallService(MS_NETLIB_STARTSSL, (WPARAM)hConnection, 0))
			//            {
			//                NetLog_Server("Unable to connect to ICQ new family server, SSL could not be negotiated");
			//                NetLib_CloseConnection(&hConnection, FALSE);
			//            }
			//        }

			//        (this->*pCookieData->familyHandler)(hConnection, pCookie, wCookieLen);

			//        // Free allocated memory
			//        // NOTE: "cookie" will get freed when we have connected to the avatar server.
			//        disposeChain(&pChain);
			//        SAFE_FREE(&pServer);
			//        SAFE_FREE((void**)&pCookieData);
			//    }

			//    break;
			//}
		}
		private static void HandleServerExtStatus(MemoryStream stream, SNACHeader header)
		{
			// our session data
			//{
			//    // Received owner session data

			//    while (wBufferLength > 4)
			//    { // loop thru all items
			//        WORD itemType = pBuffer[0] * 0x10 | pBuffer[1];
			//        BYTE itemFlags = pBuffer[2];
			//        BYTE itemLen = pBuffer[3];

			//        if (itemType == AVATAR_HASH_PHOTO) /// TODO: handle photo item
			//        { // skip photo item

			//            // Photo item recognized

			//        }
			//        else if ((itemType == AVATAR_HASH_STATIC || itemType == AVATAR_HASH_FLASH) && (itemLen >= 0x10))
			//        {

			//            // Avatar item recognized

			//            if (m_bAvatarsEnabled && !info->bMyAvatarInited) // signal the server after login
			//            { // this refreshes avatar state - it used to work automatically, but now it does not
			//                if (getSettingByte(NULL, "ForceOurAvatar", 0))
			//                { // keep our avatar
			//                    char* file = GetOwnerAvatarFileName();

			//                    SetMyAvatar(0, (LPARAM)file);
			//                    SAFE_FREE(&file);
			//                }
			//                else // only change avatar hash to the same one
			//                {
			//                    var hash = new Byte[0x14];

			//                    memcpy(hash, pBuffer, 0x14);
			//                    hash[2] = 1; // update image status
			//                    updateServAvatarHash(hash, 0x14);
			//                }
			//                info->bMyAvatarInited = TRUE;
			//                break;
			//            }
			//            // process owner avatar hash changed notification
			//            handleAvatarOwnerHash(itemType, itemFlags, pBuffer, itemLen + 4);
			//        }
			//        else if (itemType == 0x02)
			//        {

			//            // Status message item recognized
			//        }
			//        else if (itemType == 0x0E)
			//        {
			//            // Status mood item recognized
			//        }

			//        // move to next item
			//        if (wBufferLength >= itemLen + 4)
			//        {
			//            wBufferLength -= itemLen + 4;
			//            pBuffer += itemLen + 4;
			//        }
			//        else
			//        {
			//            pBuffer += wBufferLength;
			//            wBufferLength = 0;
			//        }
			//    }
			//    break;
			//}
		}
		private static void HandleError(MemoryStream stream, SNACHeader header)
		{
			// Something went wrong, probably the request for avatar family failed
			var Error = (Session.client.Available >= 2) ? stream.ReadUInt16() : 0;

            Log.Write("Got Data.Service.Error (#" + Error + ")");

			//Debugger.Break();
		}
		private static void HandleUnknownSNAC(MemoryStream stream, SNACHeader header)
		{
			// Debugger.Break();

			// Warning: Ignoring SNAC(x%02x,x%02x) - 
			// Unknown SNAC (Flags: %u, Ref: %u)", 
			// ICQ_SERVICE_FAMILY, 
			// pSnacHeader->wSubtype, 
			// pSnacHeader->wFlags, 
			// pSnacHeader->dwRef);
		}

	}
}
